﻿using System;
using GameEngine;
using GameEngine.Navigation;
using GameEngine.Units;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using SkinningInformation;
using System.Collections.Generic;
using GameEngine.Events;
using System.Diagnostics;

namespace DotNetNinja
{
	public class Game : Microsoft.Xna.Framework.Game
	{
		private World World;

		private GraphicsDeviceManager GraphicsDeviceManager;

		private float FieldOfView;
		private Matrix Projection;
		private Matrix View;

		private Vector3 cameraOffset;

		private Unit dotNetNinja;

		private int spawnCount;
		private TimeSpan spawnCooldown;
		private TimeSpan spawnTime;
		private int spawnType;

		private int points;

		SpriteBatch spriteBatch;
		SpriteFont spriteFont;

		Model bleed;
		AnimationPlayer player;

		Texture2D dotNetNinjaStatusTexture;
		Texture2D dotNetNinjaHealthBarTexture;

		Dictionary<Item, string> itemsAtSpawnPoints;
		Queue<string> itemsSpawnPointsQueue;

		public Game()
		{
			this.GraphicsDeviceManager = new GraphicsDeviceManager(this);
			this.GraphicsDeviceManager.IsFullScreen = true;
			// this.GraphicsDeviceManager.PreferMultiSampling = true;
			this.GraphicsDeviceManager.PreferMultiSampling = false;
			this.Content.RootDirectory = "Content";
			this.TargetElapsedTime = TimeSpan.FromTicks(333333);

			this.FieldOfView = MathHelper.ToRadians(45.0f);
			this.Projection = Matrix.Identity;
			this.View = Matrix.Identity;

			TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag | GestureType.Flick;
		}

		protected override void Initialize()
		{
			base.Initialize();

			this.spawnCooldown = TimeSpan.FromTicks((int)(new TimeSpan(0, 0, 0, 1).Ticks * (2 + (10f * 10f) / ((float)this.spawnCount + 10f))));
			this.spawnTime = this.spawnCooldown;
			this.points = 0;
		}

		protected override void LoadContent()
		{
			this.World = new World();

			this.World.NavigationMesh = this.Content.Load<NavigationMesh>("NavigationMap");
			this.World.NavigationMap = new NavigationMap(this.World.NavigationMesh);

			Model model;
			SkinningData skinningData;

			this.World.RegisterPoint("Shrine", new Vector3(0, 0, 0));
			this.World.RegisterPoint("ShrineItem1", new Vector3(-37, -39, 0));
			this.World.RegisterPoint("ShrineItem2", new Vector3(61, 1, 0));
			this.World.RegisterPoint("ShrineItem3", new Vector3(12, 65, 0));

			this.World.RegisterPoint("Center", new Vector3(243, 225, 0));
			this.World.RegisterPoint("CenterItem1", new Vector3(215, 197, 0));
			this.World.RegisterPoint("CenterItem2", new Vector3(295, 235, 0));
			this.World.RegisterPoint("CenterItem3", new Vector3(260, 277, 0));

			this.World.RegisterPoint("Castle", new Vector3(138, 518, 0));
			this.World.RegisterPoint("CastleItem1", new Vector3(214, 490, 0));
			this.World.RegisterPoint("CastleItem2", new Vector3(222, 551, 0));
			this.World.RegisterPoint("CastleItem3", new Vector3(164, 595, 0));

			this.World.RegisterPoint("Woods", new Vector3(530, 120, 0));
			this.World.RegisterPoint("WoodsItem1", new Vector3(568, 82, 0));
			this.World.RegisterPoint("WoodsItem2", new Vector3(591, 188, 0));
			this.World.RegisterPoint("WoodsItem3", new Vector3(520, 191, 0));

			model = this.Content.Load<Model>("IslandArena");
			this.World.RegisterDoodadType("IslandArena", model);

			model = this.Content.Load<Model>("BleedEffectModel");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterParticleType("Bleed", model, skinningData, new TimeSpan(0, 0, 0, 0, 660));

			model = this.Content.Load<Model>("PowerUpPick");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterParticleType("PowerUpPick", model, skinningData, new TimeSpan(0, 0, 0, 1, 0));

			model = this.Content.Load<Model>("DotNetNinjaModel");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterUnitType("DotNetNinja", model, skinningData, 100, 80, 6);

			model = this.Content.Load<Model>("GreenMonsterModel");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterUnitType("GreenMonster", model, skinningData, 50, 30, 6);

			model = this.Content.Load<Model>("HunchbackModel");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterUnitType("HunchBackMonster", model, skinningData, 30, 20, 6);

			this.World.CreateDoodad("IslandArena", Vector3.Zero, 0f);

			this.dotNetNinja = this.World.CreateUnit("DotNetNinja", "Center", MathHelper.PiOver2);

			model = this.Content.Load<Model>("TelerikPowerUp");
			skinningData = model.Tag as SkinningData;
			this.World.RegisterItemType("Teleriker", model, skinningData, 10, true, new List<Unit>() { this.dotNetNinja }, null);

			this.itemsAtSpawnPoints = new Dictionary<Item, string>();
			this.itemsSpawnPointsQueue = new Queue<string>();

			this.SpawnPowerUpItem("ShrineItem1");
			this.SpawnPowerUpItem("ShrineItem2");
			this.SpawnPowerUpItem("ShrineItem3");

			this.SpawnPowerUpItem("CenterItem1");
			this.SpawnPowerUpItem("CenterItem2");
			this.SpawnPowerUpItem("CenterItem3");

			this.SpawnPowerUpItem("CastleItem1");
			this.SpawnPowerUpItem("CastleItem2");
			this.SpawnPowerUpItem("CastleItem3");

			this.SpawnPowerUpItem("WoodsItem1");
			this.SpawnPowerUpItem("WoodsItem2");
			this.SpawnPowerUpItem("WoodsItem3");

			this.World.Killing += HandleKilling;
			this.World.Picking += HandlePicking;

			spawnTime = new TimeSpan(0, 0, 0, 10);

			cameraOffset = new Vector3(60, 60, 100);

			this.spriteBatch = new SpriteBatch(this.GraphicsDeviceManager.GraphicsDevice);
			spriteFont = Content.Load<SpriteFont>("SegoeUIMono");

			this.dotNetNinjaStatusTexture = this.Content.Load<Texture2D>("DotNetNinjaStatus");
			this.dotNetNinjaHealthBarTexture = this.Content.Load<Texture2D>("DotNetNinjaHealthBar");
		}

		private void SpawnPowerUpItem(string location)
		{
			itemsAtSpawnPoints.Add(this.World.CreateItem("Teleriker", location, 0, null), location);
		}

		private void HandlePicking(object sender, ItemInteractionArgs args)
		{
			if (args.Unit == this.dotNetNinja)
			{
				if (this.itemsAtSpawnPoints.ContainsKey(args.Item))
				{
					this.World.CreateParticle("PowerUpPick", args.Item.Position, 0);

					string itemSpawnPoint = this.itemsAtSpawnPoints[args.Item];
					this.itemsAtSpawnPoints.Remove(args.Item);
					this.itemsSpawnPointsQueue.Enqueue(itemSpawnPoint);
					if (this.itemsSpawnPointsQueue.Count > 6)
					{
						itemSpawnPoint = this.itemsSpawnPointsQueue.Dequeue();
						this.SpawnPowerUpItem(itemSpawnPoint);
					}
					this.points += 2;
					this.World.Heal(null, this.dotNetNinja, 5);
					// this.dotNetNinja.HitPoints = Math.Min(this.dotNetNinja.MaxHitPoints, this.dotNetNinja.HitPoints + 2);
				}
			}
		}

		private void HandleKilling(object sender, UnitInteractionArgs args)
		{
			if (args.Affected == this.dotNetNinja)
			{
			}
			else if (args.Initiator == this.dotNetNinja)
			{
				this.points += 1;
			}
		}

		protected override void UnloadContent()
		{
		}

		protected override void Update(GameTime gameTime)
		{
			if (!this.dotNetNinja.IsDead)
			{
				spawnTime = TimeSpan.FromTicks(Math.Max(0, spawnTime.Ticks - gameTime.ElapsedGameTime.Ticks));
				if (spawnTime.Ticks <= gameTime.ElapsedGameTime.Ticks)
				{
					this.spawnCooldown = TimeSpan.FromTicks((int)(new TimeSpan(0, 0, 0, 1).Ticks * (2 + (10f * 10f) / ((float)this.spawnCount + 10f))));
					spawnTime = this.spawnCooldown;
					this.spawnCount++;
					Unit monster = null;
					switch (this.spawnType)
					{
						case 0:
							monster = this.World.CreateUnit("GreenMonster", "Shrine", MathHelper.Pi);
							break;
						case 1:
							monster = this.World.CreateUnit("GreenMonster", "Castle", MathHelper.Pi);
							// monster = this.World.CreateUnit("HunchBackMonster", "Castle", MathHelper.Pi);
							break;
						case 2:
							monster = this.World.CreateUnit("GreenMonster", "Woods", MathHelper.Pi);
							// TODO: Of course one man army can not prepare all the storyboards, code, design, resources, sounds, QA, marketing etc. this model is still unavailable...
							// monster = this.World.CreateUnit("WareRabbitMonster", "Woods", MathHelper.Pi);
							break;
					}
					if (monster != null)
					{
						monster.AIScript = new KillUnit(this.dotNetNinja);
					}
					this.spawnType = (this.spawnType + 1) % 3;
				}
				else
				{
					spawnTime = TimeSpan.FromTicks(spawnTime.Ticks - gameTime.ElapsedGameTime.Ticks);
				}
			}

			Viewport viewport = this.GraphicsDeviceManager.GraphicsDevice.Viewport;

			// Get the transform to proccess input
			this.Projection = Matrix.CreatePerspectiveFieldOfView(this.FieldOfView, viewport.AspectRatio, 1.0f, 10000.0f);
			Vector3 cameraPosition = dotNetNinja.Position + cameraOffset;
			Vector3 cameraTarget = dotNetNinja.Position;
			this.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, new Vector3(0, 0, 1));

			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
			{
				this.Exit();
			}

			IList<WayPoint> wayPoints = this.World.NavigationMap.WayPoints;

			TouchCollection touches = TouchPanel.GetState();

			if (!this.dotNetNinja.IsDead)
			{
				while (TouchPanel.IsGestureAvailable)
				{
					GestureSample gesture = TouchPanel.ReadGesture();
					//Debug.WriteLine("Gesture: {0} {1}", gesture.GestureType, gameTime.TotalGameTime);
					switch (gesture.GestureType)
					{
						case GestureType.DoubleTap:
							break;
						case GestureType.DragComplete:
							break;
						case GestureType.Flick:
							// TODO: Issue an Order instead of set action...
							if (touches.Count == 1)
							{
								Ray flickCenter = GetRayFromScreenPosition(touches[0].Position);
								Plane plane = new Plane(0, 0, 1, -this.dotNetNinja.Position.Z);
								float? distance = flickCenter.Intersects(plane);
								Vector3 clickPoint1 = flickCenter.Position + (flickCenter.Direction * distance.Value);
								Vector3 direction = clickPoint1 - this.dotNetNinja.Position;
								this.dotNetNinja.Rotation = (float)Math.Atan2(direction.Y, direction.X);
							}
							this.dotNetNinja.Action = new SlashAttack(this.dotNetNinja);
							break;
						case GestureType.FreeDrag:
							break;
						case GestureType.Hold:
							break;
						case GestureType.HorizontalDrag:
							break;
						case GestureType.None:
							break;
						case GestureType.Pinch:
							break;
						case GestureType.PinchComplete:
							break;
						case GestureType.Tap:
							Ray clickRay = GetRayFromScreenPosition(gesture.Position);

							Unit unit = this.dotNetNinja;

							for (int i = 0; i < wayPoints.Count; i++)
							{
								WayPoint clickedWayPoint = wayPoints[i];
								BoundingSphere s = new BoundingSphere(clickedWayPoint.Position, 4);
								float? intersects = s.Intersects(clickRay);
								if (intersects != null)
								{
									this.dotNetNinja.MoveTo(clickedWayPoint);
									break;
								}
							}
							break;
						case GestureType.VerticalDrag:
							break;
						default:
							break;
					}
				}
			}

			if (this.World != null)
			{
				this.World.Update(gameTime);
			}

			this.Projection = Matrix.CreatePerspectiveFieldOfView(this.FieldOfView, viewport.AspectRatio, 1.0f, 10000.0f);
			cameraPosition = dotNetNinja.Position + cameraOffset;
			cameraTarget = dotNetNinja.Position;
			this.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, new Vector3(0, 0, 1));
		}

		private Ray GetRayFromScreenPosition(Vector2 position)
		{
			Vector3 nearSource = new Vector3(position, 0f);
			Vector3 farSource = new Vector3(position, 1f);
			Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, this.Projection, this.View, Matrix.Identity);
			Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource, this.Projection, this.View, Matrix.Identity);
			Vector3 direction = farPoint - nearPoint;
			direction.Normalize();
			Ray clickRay = new Ray(nearPoint, direction);
			return clickRay;
		}

		private static WayPoint FindClosestWayPoint(WayPoint[] wayPoints, ref Vector3 target)
		{
			WayPoint closestWaypoint = null;
			float distance = float.PositiveInfinity;
			foreach (WayPoint wayPoint in wayPoints)
			{
				float newDistance = Vector3.Distance(wayPoint.Position, target);
				if (newDistance < distance)
				{
					closestWaypoint = wayPoint;
					distance = newDistance;
				}
			}
			return closestWaypoint;
		}

		protected override void Draw(GameTime gameTime)
		{
			Viewport viewport = this.GraphicsDevice.Viewport;

			this.GraphicsDevice.Clear(Color.Black);
			this.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
			this.GraphicsDevice.BlendState = BlendState.Opaque;
			
			if (this.World != null)
			{
				this.World.Draw(gameTime, this.GraphicsDevice, ref this.View, ref this.Projection);
			}

			spriteBatch.Begin();
			Vector2 textSize;

			float hp = MathHelper.Max(0, this.dotNetNinja.HitPoints);
			float maxHp = this.dotNetNinja.MaxHitPoints;

			// DotNetNinjaStatus sprite
			spriteBatch.Draw(this.dotNetNinjaStatusTexture, new Vector2(0, viewport.Height), null, Color.White, 0.0f, new Vector2(0, this.dotNetNinjaStatusTexture.Height), 1.0f, SpriteEffects.None, 0.0f);
			spriteBatch.Draw(this.dotNetNinjaHealthBarTexture, new Rectangle(1, viewport.Height - this.dotNetNinjaHealthBarTexture.Height - 1, (int)(this.dotNetNinjaHealthBarTexture.Width * hp / maxHp), this.dotNetNinjaHealthBarTexture.Height), Color.White);

			String healthText = String.Format("{0}/{1}", hp, maxHp);
			float hpPerCent = hp / maxHp;
			Color color = new Color(255, hpPerCent, hpPerCent);
			textSize = spriteFont.MeasureString(healthText);
			spriteBatch.DrawString(spriteFont, healthText, new Vector2(150 / 2, viewport.Height - 6), color, 0f, new Vector2(textSize.X / 2, textSize.Y / 2), 0.8f, SpriteEffects.None, 0f);

			String pointsText = String.Format("POINTS: {0}", this.points);
			textSize = spriteFont.MeasureString(pointsText);
			spriteBatch.DrawString(spriteFont, pointsText, new Vector2(viewport.Width - 2, viewport.Height - 2), Color.White, 0f, new Vector2(textSize.X, textSize.Y), 0.8f, SpriteEffects.None, 0f);

			////// Level
			////string spawnText = String.Format("#{0}", this.spawnCount);
			////textSize = spriteFont.MeasureString(spawnText);
			////position = new Vector2(10, this.GraphicsDevice.Viewport.Height - textSize.Y - 3);
			////spriteBatch.DrawString(spriteFont, spawnText, position, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);

			////// Respawn time
			////string spawnTimeText = String.Format("{0}.{1:00}s", this.spawnCooldown.Seconds, this.spawnCooldown.Milliseconds / 10);
			////textSize = spriteFont.MeasureString(spawnTimeText);
			////position = new Vector2(this.GraphicsDevice.Viewport.Width - textSize.X - 10, this.GraphicsDevice.Viewport.Height - textSize.Y - 3);
			////spriteBatch.DrawString(spriteFont, spawnTimeText, position, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);

			spriteBatch.End();

			// TODO: Draw effects
			// this.GraphicsDevice.BlendState = BlendState.Additive;
		}
	}
}
